package com.nekoneko.nekonekodemo.ui;

import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.ScrollView;


/**
 * Created by 想法的猫 on 2018/1/18 0018.
 */

public class OverScrollView extends ScrollView implements ValueAnimator.AnimatorUpdateListener {
    private int originalTop = 0;
    private int originalBottom = 0;
    private int scaleTargetOriginalWidth;
    private int scaleTargetOriginalHeight;
    private final static int scaleViewResetAnimDuration = 200;
    private final static int MOVE_DOWN_DIRECTION = -1;
    private final static int MOVE_UP_DIRECTION = 1;
    private int bottomMaxMove = 400;//dp

    String TAG = "OverScrollView";
    ValueAnimator valueAnimator = ValueAnimator.ofInt();
    ValueAnimator bottomValueAnimator = ValueAnimator.ofInt();
    View topScaleTargetView;

    public void setTopScaleTargetView(View topScaleTargetView) {
        this.topScaleTargetView = topScaleTargetView;

    }

    /**
     * 根据手机分辨率从DP转成PX
     *
     * @param context
     * @param dpValue
     * @return
     */
    public static int dip2px(Context context, float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public OverScrollView(Context context) {
        super(context);
        init();
        initAnimator();
    }

    public void setBottomMaxMove(int bottomMaxMove) {
        bottomMaxMove = dip2px(getContext(), bottomMaxMove);
        this.bottomMaxMove = bottomMaxMove;
    }

    private void init() {
        setBottomMaxMove(bottomMaxMove);
    }

    public OverScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAnimator();
        init();
    }

    public OverScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAnimator();
        init();
    }

    /**
     * 初始化动画
     */
    private void initAnimator() {
        DecelerateInterpolator interpolator = new DecelerateInterpolator();
        valueAnimator.setInterpolator(interpolator);
        valueAnimator.addUpdateListener(this);
        bottomValueAnimator.setInterpolator(interpolator);
    }

    private void recordTargetOriginalInfo() {
        if (topScaleTargetView != null) {
            scaleTargetOriginalWidth = topScaleTargetView.getWidth();
            scaleTargetOriginalHeight = topScaleTargetView.getHeight();
        }
    }

    private void recordOriginalPosition() {
        originalTop = getTop();
        originalBottom = getBottom();
    }

    private boolean isTargetScale() {
        return topScaleTargetView != null && !(topScaleTargetView.getHeight() == scaleTargetOriginalHeight && topScaleTargetView.getWidth() == scaleTargetOriginalWidth);
    }

    private boolean isBottomScale() {
        return getBottom() != originalBottom;
    }

    boolean hasDown = false;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("dispatchTouchEvent", "valueAnimator.isRunning()=" + valueAnimator.isRunning() + " bottomValueAnimator.isRunning()=" + bottomValueAnimator.isRunning());
        if (valueAnimator.isRunning() || bottomValueAnimator.isRunning()) {
            return true;
        }
        int action = ev.getAction() & MotionEvent.ACTION_MASK;
        if (action != MotionEvent.ACTION_DOWN && !hasDown) {
            return true;
        }
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                hasDown = true;
                recordLastY(ev);
                recordTargetOriginalInfo();
                recordOriginalPosition();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                recordLastY(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                int currentPointerId = getPointerId(ev);
                int lastEvY = lastYMap.get(currentPointerId);
                float v = getEventRawY(ev) - lastEvY;
                boolean canScrollUp = canScrollVertically(MOVE_DOWN_DIRECTION);//不能往上滚动
                boolean canScrollDown = canScrollVertically(MOVE_UP_DIRECTION);
                recordLastY(ev);
                int moveDirection = v > 0 ? MOVE_DOWN_DIRECTION : MOVE_UP_DIRECTION;
                //头部的过渡拉伸效果
                if (checkScaleTopView(v, canScrollUp, moveDirection)) {
                    return true;
                }
                //底部的过渡拉伸效果
                if (interceptMoveUp((int) v, canScrollDown, moveDirection)) {
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                hasDown = false;
                resetTargetView();
                resetBottomScale();
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    SparseIntArray lastYMap = new SparseIntArray();

    private int getPointerId(MotionEvent ev) {
        return ev.getPointerId(ev.getActionIndex());
    }

    private void recordLastY(MotionEvent ev) {
        lastYMap.put(getPointerId(ev), (int) getEventRawY(ev));
    }

    private float getEventRawY(MotionEvent ev) {
        return ev.getY(ev.getActionIndex()) + getTop();
    }


    /**
     * 底部拉伸的判断
     *
     * @param moveY
     * @param canScrollDown
     * @param moveDirection
     * @return
     */
    private boolean interceptMoveUp(int moveY, boolean canScrollDown, int moveDirection) {
        if (moveY < 0) {
            moveY = moveY / 3;
        }
        if ((!canScrollDown && moveDirection == MOVE_UP_DIRECTION) || isBottomScale()) {
            int top = getTop() + moveY;
            if (top > originalTop) {
                top = originalTop;
            }
            int bottom = getBottom() + moveY;
            if (bottom > originalBottom) {
                bottom = originalBottom;
            }

            int minTop = originalTop - bottomMaxMove;
            int minBottom = originalBottom - bottomMaxMove;
            if (top < minTop) {
                top = minTop;
            }
            if (bottom < minBottom) {
                bottom = minBottom;
            }
            layout(getLeft(), top, getRight(), bottom);
            return true;
        }
        return false;
    }


    private void updateScaleViewLayout(int height) {
        if (topScaleTargetView == null) {
            return;
        }
        if (height < scaleTargetOriginalHeight) {
            height = scaleTargetOriginalHeight;
        }
        if (height == topScaleTargetView.getHeight()) {
            return;
        }
        topScaleTargetView.getLayoutParams().height = height;
        topScaleTargetView.requestLayout();
    }


    private boolean checkScaleTopView(float v, boolean canScrollUp, int moveDirection) {
        if (topScaleTargetView != null && !isBottomScale() && !canScrollUp && (moveDirection == MOVE_DOWN_DIRECTION || isTargetScale())) {
            if (v > 0) {
                v = v / 3;
            }
            int height = (int) (topScaleTargetView.getHeight() + v);
            int maxHeight = (int) (scaleTargetOriginalHeight * 1.5);
            if (height > maxHeight) {
                height = maxHeight;
            }
            updateScaleViewLayout(height);
            return true;
        }
        return false;
    }

    private void resetTargetView() {
        if (topScaleTargetView == null) {
            return;
        }
        if (scaleTargetOriginalHeight == topScaleTargetView.getHeight() && scaleTargetOriginalWidth == topScaleTargetView.getWidth()) {
            return;
        }
        valueAnimator.setIntValues(topScaleTargetView.getHeight(), scaleTargetOriginalHeight);
        float speed = (float) (topScaleTargetView.getHeight()) / scaleTargetOriginalHeight;
        int duration = (int) (scaleViewResetAnimDuration / speed);
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }

    private void resetBottomScale() {
        int top = getTop();
        if (top == originalTop) {
            return;
        }
        int moveY = top - originalTop;
        bottomValueAnimator.setIntValues(moveY, 0);
        float speed = (float) (1.5 - Math.abs((float) moveY / bottomMaxMove));
        int duration = (int) (speed * scaleViewResetAnimDuration);
        bottomValueAnimator.setDuration(Math.abs(duration));
        bottomValueAnimator.start();
        bottomValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animValue = (int) animation.getAnimatedValue();
                layout(getLeft(), originalTop + animValue, getRight(), originalBottom + animValue);
            }
        });
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        updateScaleViewLayout((Integer) animation.getAnimatedValue());
    }
}
